#ifndef __COOLTABCTRLS_H__
#define __COOLTABCTRLS_H__

#pragma once

/////////////////////////////////////////////////////////////////////////////
// CCoolTabCtrls - A set of Tab Controls with different appearances
//
// Written by Bjarke Viksoe (bjarke@viksoe.dk)
// Several improvements done by Daniel Bowen.
// Copyright (c) 2001-2002 Bjarke Viksoe.
//
// Add the following macro to the parent's message map:
//   REFLECT_NOTIFICATIONS()
// CFolderTabCtrl code based on a Paul DiLascia MSJ 1999 article.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed by any means PROVIDING it is 
// not sold for profit without the authors written consent, and 
// providing that this notice and the authors name is included. 
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever. It's free, so don't hassle me about it.
//
// Beware of bugs.
//

#ifndef __cplusplus
  #error WTL requires C++ compilation (use a .cpp suffix)
#endif

#ifndef __ATLAPP_H__
  #error CoolTabCtrls.h requires atlapp.h to be included first
#endif

#ifndef __ATLCTRLS_H__
  #error CoolTabCtrls.h requires atlctrls.h to be included first
#endif

#ifndef __ATLGDIX_H__
  #error CoolTabCtrls.h requires atlgdix.h to be included first
#endif

#if (_WIN32_IE < 0x0400)
  #error CoolTabCtrls.h requires _WIN32_IE >= 0x0400
#endif


#ifndef TCN_INITIALIZE
   #define TCN_INITIALIZE TCN_FIRST-10
   #define TCN_INSERTITEM TCN_FIRST-11
   #define TCN_DELETEITEM TCN_FIRST-12
#endif // TCN_INITIALIZE

struct TC_SIZES
{
   int iPadding;
   int iMargin;
   int iSelMargin;
   int iIndent;
};

template< class T, class TBase = CStatic, class TWinTraits = CControlWinTraits >
class ATL_NO_VTABLE CCustomTabCtrl : 
   public CWindowImpl< T, TBase, TWinTraits >,
   public COffscreenDrawRect< T >
{
public:
   DECLARE_WND_SUPERCLASS(NULL, TBase::GetWndClassName())

   CCustomTabCtrl() : m_iCurSel(-1)
   {
      ::ZeroMemory(&m_settings, sizeof(TC_SIZES));
   }

   // Data members

   int m_iCurSel;
   TC_SIZES m_settings;
   CSimpleArray< LPTCITEM > m_Items;
   CSimpleValArray< RECT > m_Sizes;
   UINT m_idDlgCtrl;

   // Operations

   BOOL SubclassWindow(HWND hWnd)
   {
      ATLASSERT(m_hWnd==NULL);
      ATLASSERT(::IsWindow(hWnd));
      BOOL bRet = CWindowImpl< T, TBase, TWinTraits >::SubclassWindow(hWnd);
      if( bRet ) _Init();
      return bRet;
   }

   // Implementation

   void _Init()
   {
      ATLASSERT(::IsWindow(m_hWnd));

      // Derived from STATIC
      ATLASSERT(GetStyle() & WS_CHILD);
      ModifyStyle(0, SS_NOTIFY); // We need this for mouse-clicks

      m_idDlgCtrl = GetDlgCtrlID();

      T* pT = static_cast<T*>(this);
      BOOL bDummy;
      pT->OnSettingChange(0, 0, 0, bDummy);

      // This is a little WTL subclass helper notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_INITIALIZE };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
   }

   void _InflateSize(int i, int cx, int cy)
   {
      ATLASSERT(i>=0 && i<m_Sizes.GetSize());
      RECT rc = m_Sizes[i];
      ::InflateRect(&rc, cx,cy);
      m_Sizes.SetAtIndex(i, rc);
   }

   // Message map and handlers

   BEGIN_MSG_MAP(CCustomTabCtrl)
      CHAIN_MSG_MAP(COffscreenDrawRect< T >)
      MESSAGE_HANDLER(WM_CREATE, OnCreate)
      MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
      MESSAGE_HANDLER(WM_SIZE, OnSize)
      MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonClick)
      MESSAGE_HANDLER(WM_RBUTTONDOWN, OnRButtonClick)
      MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
      MESSAGE_HANDLER(WM_GETDLGCODE, OnGetDlgCode)
      MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
   END_MSG_MAP()

   LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      LRESULT lRes = DefWindowProc();
      _Init();
      return lRes;
   }
   LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
   {
      DeleteAllItems();
      bHandled = FALSE;
      return 0;
   }
   LRESULT OnSize(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
   {
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      bHandled = FALSE;
      return 0;
   }
   LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // Empty - but might get overloaded by derived classes.
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return 0;
   }

   LRESULT OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
   {
      return DefWindowProc(uMsg, wParam, lParam) | DLGC_WANTARROWS;
   }

   LRESULT OnLButtonClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
   {
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, NM_CLICK };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);

      // Search for a tab
      T* pT = static_cast<T*>(this);
      POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
      TCHITTESTINFO tchti = { 0 };
      tchti.pt = pt;
      int idx = pT->HitTest(&tchti);
      if( idx != -1 ) {
         SetFocus();
         SetCurSel(idx);
      }
      return 0;
   }

   LRESULT OnRButtonClick(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM lParam, BOOL& /*bHandled*/)
   {
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, NM_RCLICK };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);

      // Search for a tab
      T* pT = static_cast<T*>(this);
      POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
      TCHITTESTINFO tchti = { 0 };
      tchti.pt = pt;
      int idx = pT->HitTest(&tchti);
      if( idx != -1 ) {
         SetFocus();
         SetCurSel(idx);
      }
      return 0;
   }

   LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
   {
      switch( wParam ) {
      case VK_LEFT:
         if( m_iCurSel > 0 ) SetCurSel(m_iCurSel - 1);
         return 0;
      case VK_RIGHT:
         if( m_iCurSel < m_Items.GetSize() - 1 ) SetCurSel(m_iCurSel + 1);
         return 0;
      }
      bHandled = FALSE;
      return 0;
   }

   // Overridables

   void UpdateLayout()
   {
      CClientDC dc(m_hWnd);
      HFONT hOldFont = (HFONT) ::SelectObject(dc, GetFont());    

      RECT rcClient;
      GetClientRect(&rcClient);

      // Reposition buttons
      int nCount = m_Sizes.GetSize();
      int xpos = m_settings.iIndent;
      if( GetStyle() & TCS_RAGGEDRIGHT ) {
         for( int i = nCount; i >= 0; i-- ) {
            LPTCITEM pItem = m_Items[i];
            RECT rc;
            rc.left = rc.right = xpos;
            if( pItem->mask & TCIF_TEXT ) {
               RECT rcText = { 0 };
               dc.DrawText(pItem->pszText, ::lstrlen(pItem->pszText), &rcText, DT_SINGLELINE|DT_CALCRECT);
               rc.right += (rcText.right - rcText.left) + (m_settings.iPadding * 2);
            }
            rc.top = 0;
            rc.bottom = rcClient.bottom - rcClient.top;
            m_Sizes.SetAtIndex(i, rc);
            xpos += (rc.right - rc.left) + m_settings.iMargin;
         }
      }
      else {
         for( int i = 0; i < nCount; i++ ) {
            LPTCITEM pItem = m_Items[i];
            RECT rc;
            rc.left = rc.right = xpos;
            if( pItem->mask & TCIF_TEXT ) {
               RECT rcText = { 0 };
               dc.DrawText(pItem->pszText, ::lstrlen(pItem->pszText), &rcText, DT_SINGLELINE|DT_CALCRECT);
               rc.right += (rcText.right - rcText.left) + (m_settings.iPadding * 2);
            }
            rc.top = 0;
            rc.bottom = rcClient.bottom - rcClient.top;
            m_Sizes.SetAtIndex(i, rc);
            xpos += (rc.right - rc.left) + m_settings.iMargin;
         }
      }
      if( m_iCurSel != -1 ) _InflateSize(m_iCurSel, m_settings.iSelMargin, 0);
      
      ::SelectObject(dc, hOldFont);
   }

   void DoPaint(CDCHandle dc, RECT &rcClip)
   {
      // NOTE: The handling of NM_CUSTOMDRAW is probably not entirely correct
      //       in the code below. But at least it makes a brave attempt to
      //       implement all the states described in MSDN.

      // Save current DC selections
      int save = ::SaveDC(dc);
      ATLASSERT(save!=0);

      // Make sure we don't paint outside client area (possible with paint dc)
      RECT rcClient;
      GetClientRect(&rcClient);
      ::IntersectClipRect(dc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);

      dc.FillRect(&rcClient, ::GetSysColorBrush(COLOR_3DFACE));

      // Prepare DC
      ::SelectObject(dc, GetFont());

      T* pT = static_cast<T*>(this);
      LRESULT lResStage;
      NMCUSTOMDRAW nmc = { 0 };
      nmc.hdr.hwndFrom = m_hWnd;
      nmc.hdr.idFrom = m_idDlgCtrl;
      nmc.hdr.code = NM_CUSTOMDRAW;
      nmc.hdc = dc;
      nmc.uItemState = 0;

      nmc.dwDrawStage = CDDS_PREPAINT;
      lResStage = ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
      if( lResStage == CDRF_NOTIFYITEMDRAW || lResStage == CDRF_DODEFAULT ) {
         RECT rc;
         int nCount = m_Items.GetSize();
         if( nCount > 0 ) {
            // Draw the list items, except the selected one. It is drawn last
            // so it can cover the tabs below it.
            RECT rcIntersect;
            for( int i = 0; i < nCount; i++ ) {
               rc = m_Sizes[i];
               if( rc.bottom - rc.top == 0 ) pT->UpdateLayout();
               if( i != m_iCurSel ) {
                  if( ::IntersectRect(&rcIntersect, &rc, &rcClip) ) {
                     nmc.dwItemSpec = i;
                     nmc.uItemState = 0;
                     nmc.rc = rc;
                     pT->ProcessItem(lResStage, nmc);
                  }
               }
            }
            if( m_iCurSel != -1 ) {
               rc = m_Sizes[m_iCurSel];
               if( ::IntersectRect(&rcIntersect, &rc, &rcClip) ) {
                  nmc.dwItemSpec = m_iCurSel;
                  nmc.uItemState = CDIS_SELECTED;
                  nmc.rc = rc;
                  pT->ProcessItem(lResStage, nmc);
                  nmc.uItemState = 0;
               }
            }
         }
         nmc.dwItemSpec = 0;
      }

      if( lResStage == CDRF_NOTIFYPOSTPAINT ) {
         nmc.dwDrawStage = CDDS_POSTPAINT;
         ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
      }

      ::RestoreDC(dc, save);
   }

   void ProcessItem(LRESULT lResStage, NMCUSTOMDRAW &nmc)
   {
      LRESULT lResItem = CDRF_DODEFAULT;
      if( lResStage == CDRF_NOTIFYITEMDRAW ) {
         nmc.dwDrawStage = CDDS_ITEMPREPAINT;
         lResItem = ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
      }
      if( lResItem != CDRF_SKIPDEFAULT ) {
         // Do default item-drawing
         T* pT = static_cast<T*>(this);
         pT->DoItemPaint(nmc);
      }
      if( lResStage == CDRF_NOTIFYITEMDRAW && lResItem == CDRF_NOTIFYPOSTPAINT ) {
         nmc.dwDrawStage = CDDS_ITEMPOSTPAINT;
         ::SendMessage(GetParent(), WM_NOTIFY, nmc.hdr.idFrom, (LPARAM) &nmc);
      }
   }

   void DoItemPaint(NMCUSTOMDRAW &/*nmc*/)
   {
   }

   // Operations

#define CHECK_ITEM(iItem) \
   ATLASSERT(iItem>=0 && iItem<m_Items.GetSize()); \
   if( iItem<0 || iItem>=m_Items.GetSize() ) return 0

   int InsertItem(int iItem, const LPTCITEM pItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(pItem);
      ATLASSERT(iItem==m_Items.GetSize()); // We only support appending right now
      if( iItem < 0 || iItem > m_Items.GetSize() ) iItem = m_Items.GetSize();
      LPTCITEM pNewItem;
      RECT rc = { 0 };
      ATLTRY( pNewItem = new TCITEM );
      if( pNewItem == NULL ) return -1;
      ::ZeroMemory(pNewItem, sizeof(TCITEM));
      m_Items.Add(pNewItem);
      m_Sizes.Add(rc);
      int idx = m_Items.GetSize() - 1;
      if( SetItem(idx, pItem) == FALSE ) return FALSE;
      // Send notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_INSERTITEM };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
      // Select if first tab
      if( m_Items.GetSize() == 1 ) SetCurSel(0);
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return idx;
   }

   BOOL DeleteItem(int iItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      if( iItem < 0 || iItem >= m_Items.GetSize() ) return FALSE;
      // If currently selected, select something else
      if( iItem < m_iCurSel ) {
         // The item being removed is before the current selection.
         // We still want the same item to be selected, but
         // the index needs to be adjusted to account for the missing item
         m_iCurSel--;
      }
      else if( iItem == m_iCurSel ) {
         SetCurSel( (m_Items.GetSize() > 1) ? 0 : -1 );
      }
      // Remove from structures
      LPTCITEM pItem = m_Items[iItem];
      m_Items.RemoveAt(iItem);
      m_Sizes.RemoveAt(iItem);
      if( pItem->mask & TCIF_TEXT ) ATLTRY( delete [] pItem->pszText );
      ATLTRY( delete pItem );
      // Send notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_DELETEITEM };
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
      // Repaint
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return TRUE;
   }

   BOOL DeleteAllItems()
   {
      ATLASSERT(::IsWindow(m_hWnd));
      while( GetItemCount() > 0 ) DeleteItem(0); // Slooow!!!
      return TRUE;
   }

   BOOL SetItem(int iItem, LPTCITEM pItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(!::IsBadReadPtr(pItem,sizeof(TCITEM)));
      CHECK_ITEM(iItem);

      // Copy caller's data to the internal structure
      LPTCITEM pItemT = m_Items[iItem];
      UINT mask = pItem->mask;
      if( mask & TCIF_TEXT ) {
         ATLASSERT(!::IsBadStringPtr(pItem->pszText,(UINT)-1));
         if( pItemT->mask & TCIF_TEXT ) ATLTRY( delete [] pItemT->pszText );
         ATLTRY( pItemT->pszText = new TCHAR[ ::lstrlen(pItem->pszText) + 1 ] );
         ::lstrcpy(pItemT->pszText, pItem->pszText);
         pItemT->mask |= TCIF_TEXT;
      }
      if( mask & TCIF_IMAGE ) {
         pItemT->iImage = pItem->iImage;
         pItemT->mask |= TCIF_IMAGE;
      }
      if( mask & TCIF_PARAM ) {
         pItemT->lParam = pItem->lParam;
         pItemT->mask |= TCIF_PARAM;
      }

      // Repaint control
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return TRUE;
   }

   BOOL GetItem(int iItem, LPTCITEM pItem) const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(!::IsBadWritePtr(pItem,sizeof(TCITEM)));
      CHECK_ITEM(iItem);

      // Copy item data
      LPTCITEM pItemT = m_Items[iItem];
      UINT mask = pItem->mask;
      UINT maskT = pItemT->mask;
      if( mask & TCIF_TEXT ) {
         if( maskT & TCIF_TEXT ) {
             ATLASSERT(!::IsBadWritePtr(pItem->pszText, pItem->cchTextMax));
             ::lstrcpyn( pItem->pszText, pItemT->pszText == NULL ? _T("") : pItemT->pszText, pItem->cchTextMax );
         }
         else {
             pItem->mask &= ~(TCIF_TEXT);
             if( pItem->pszText != NULL ) {
                 ::lstrcpy( pItem->pszText, _T(""));
             }
         }
      }
      if( mask & TCIF_IMAGE ) {
         if( maskT & TCIF_IMAGE ) {
            pItem->iImage = pItemT->iImage;
         }
         else {
            pItem->mask &= ~(TCIF_IMAGE);
            pItem->iImage = -1;
         }
      }
      if( mask & TCIF_PARAM ) {
         if( maskT & TCIF_PARAM ) {
            pItem->lParam = pItemT->lParam;
         }
         else {
            pItem->mask &= ~(TCIF_PARAM);
            pItem->lParam = 0;
         }
      }
      return 0;
   }

   int SetCurSel(int iItem)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      // Selecting same tab? Not worth it.
      if( iItem == m_iCurSel ) return m_iCurSel;
      if( iItem < -1 || iItem >= m_Items.GetSize() ) return m_iCurSel;

      RECT rc = { 0 };
      int iOldSel = m_iCurSel;
      // Send notification
      NMHDR nmh = { m_hWnd, m_idDlgCtrl, TCN_SELCHANGING };
      if( ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh) == TRUE ) return -1;
      // Repaint old button area
      GetItemRect(iItem, &rc); InvalidateRect(&rc);
      GetItemRect(iOldSel, &rc); InvalidateRect(&rc);

      // Change tab
      m_iCurSel = iItem;
      // Recalc new positions
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();

      // Repaint whole tab
      GetClientRect(&rc); InvalidateRect(&rc);

      // Send notification
      nmh.code = TCN_SELCHANGE;
      ::SendMessage(GetParent(), WM_NOTIFY, nmh.idFrom, (LPARAM) &nmh);
      return iOldSel;
   }

   int GetCurSel() const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      return m_iCurSel;
   }

   int GetItemCount() const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      return m_Items.GetSize();
   }

   int HitTest(LPTCHITTESTINFO pinfo) const
   {
      ATLASSERT(!::IsBadWritePtr(pinfo,sizeof(TCHITTESTINFO)));
      RECT rc;
      int nCount = m_Items.GetSize();
      for( int i = 0; i < nCount; i++ ) {
         GetItemRect(i, &rc);
         if( ::PtInRect(&rc, pinfo->pt) ) {
            pinfo->flags = TCHT_ONITEM;
            return i;
         }
      }
      return -1;
   }

   int GetRowCount() const
   {
      return 1;
   }

   DWORD SetItemSize(int iItem, int cx, int cy)
   {
      ATLASSERT(::IsWindow(m_hWnd));
      CHECK_ITEM(iItem);

      RECT rcOld = m_Sizes[iItem];
      RECT rcNew = { rcOld.left, rcOld.top, rcOld.left + cx, rcOld.top cy };
      m_Sizes[iItem] = rcNew;
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
      return MAKELONG(rcOld.right-rcOld.left, rcOld.bottom-rcOld.top);
   }

   BOOL GetItemRect(int iItem, RECT *prcItem) const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      ATLASSERT(prcItem);
      if( prcItem == NULL ) return FALSE;
      if( iItem < 0 || iItem >= m_Sizes.GetSize() ) {
         ::SetRectEmpty(prcItem);
         return FALSE;
      }
      *prcItem = m_Sizes[iItem];
      return TRUE;
   }

   void SetPadding(int iPadding) 
   { 
      m_iPadding = iPadding; 
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
   };

   void GetSizeSettings(TC_SIZES *pSizes) const
   {
      ATLASSERT(!::IsBadWritePtr(pSizes,sizeof(TC_SIZES)));
      *pSizes = m_settings;
   }

   void SetSizeSettings(const TC_SIZES *pSizes)
   {
      ATLASSERT(!::IsBadReadPtr(pSizes,sizeof(TC_SIZES)));
      m_settings = *pSizes;
      T* pT = static_cast<T*>(this);
      pT->UpdateLayout();
      Invalidate();
   }

   int FindItem(LPTCITEM pFindInfo, int nStart = -1) const
   {
      ATLASSERT(::IsWindow(m_hWnd));
      if(nStart < 0) nStart = -1;
      
      UINT maskFind = pFindInfo->mask;

      // Find the next item matching the criteria specified
      int nCount = m_Sizes.GetSize();
      for( int i = nStart + 1; i < nCount; i++ ) {
         LPTCITEM pItem = m_Items[i];
         UINT mask = pItem->mask;
         // Rewrote this for readability...
         if( (maskFind & mask) != maskFind ) continue;
         if( (maskFind & TCIF_PARAM) == TCIF_PARAM && 
            pItem->lParam != pFindInfo->lParam ) continue;
         if( (maskFind & TCIF_TEXT) == TCIF_TEXT &&
            pItem->pszText != NULL &&
            pFindInfo->pszText != NULL &&
            ::lstrcmp(pItem->pszText, pFindInfo->pszText) != 0 ) continue;
         if( (maskFind & TCIF_IMAGE) == TCIF_IMAGE && 
            pItem->iImage != pFindInfo->iImage ) continue;
         if( (maskFind & TCIF_STATE) == TCIF_STATE && 
            pItem->dwState != pFindInfo->dwState ) continue;
         return i;
      }

      return -1;
   }
};


/////////////////////////////////////////////////////////////////////////////
//
// The sample tab controls
//
// The follwing samples derive directly from CCustomTabCtrl.
// This means that they can actually use the internal members
// of this class. But they will not! To keep the code clean, I'm only
// using public member methods to access all variables.
//
// You need to add the...
//   REFLECT_NOTIFICATIONS()
// macro to the parent's message map.
//

class CButtonTabCtrl : 
   public CCustomTabCtrl<CButtonTabCtrl>,
   public CCustomDraw<CButtonTabCtrl>
{
public:
   DECLARE_WND_SUPERCLASS(_T("WTL_CoolButtonTabCtrl"), GetWndClassName())  

   BEGIN_MSG_MAP(CButtonTabCtrl)
      CHAIN_MSG_MAP(CCustomTabCtrl<CButtonTabCtrl>)
      CHAIN_MSG_MAP_ALT(CCustomDraw<CButtonTabCtrl>, 1)
      REFLECTED_NOTIFY_CODE_HANDLER(TCN_INITIALIZE, OnInitialize)
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   LRESULT OnInitialize(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
   {
      TC_SIZES sizes;
      GetSizeSettings(&sizes);
      sizes.iPadding = 10;
      sizes.iMargin = 3;
      SetSizeSettings(&sizes);
      return 0;
   }

   DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
   {
      return CDRF_NOTIFYITEMDRAW;   // We need per-item notifications
   }

   DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      ::SetBkMode(lpNMCustomDraw->hdc, TRANSPARENT);
      ::SetTextColor(lpNMCustomDraw->hdc, ::GetSysColor(COLOR_BTNTEXT));
      
      UINT state = 0;
      if( lpNMCustomDraw->uItemState & CDIS_SELECTED ) state |= DFCS_PUSHED;
      if( lpNMCustomDraw->uItemState & CDIS_DISABLED ) state |= DFCS_INACTIVE;
      ::DrawFrameControl(lpNMCustomDraw->hdc, &lpNMCustomDraw->rc, DFC_BUTTON, DFCS_BUTTONPUSH | state );
      
      TCITEM item = { 0 };
      TCHAR szText[128];
      item.mask = TCIF_TEXT;
      item.pszText = szText;
      item.cchTextMax = sizeof(szText)/sizeof(TCHAR);
      GetItem(lpNMCustomDraw->dwItemSpec, &item);
      if( lpNMCustomDraw->uItemState & CDIS_SELECTED ) {
         lpNMCustomDraw->rc.left += 2;
         lpNMCustomDraw->rc.top += 2;
      }
      ::DrawText(lpNMCustomDraw->hdc, item.pszText, ::lstrlen(item.pszText), &lpNMCustomDraw->rc, DT_SINGLELINE|DT_CENTER|DT_VCENTER);

      return CDRF_SKIPDEFAULT;
   }
};

class CFolderTabCtrl : 
   public CCustomTabCtrl<CFolderTabCtrl>,
   public CCustomDraw<CFolderTabCtrl>
{
public:
   DECLARE_WND_SUPERCLASS(_T("WTL_CoolFolderTabCtrl"), GetWndClassName())  

   CFont m_font;

   enum { CXOFFSET = 8 };     // defined pitch of trapezoid slant
   enum { CXMARGIN = 2 };     // left/right text margin
   enum { CYMARGIN = 1 };     // top/bottom text margin
   enum { CYBORDER = 1 };     // top border thickness
 
   BEGIN_MSG_MAP(CFolderTabCtrl)
      MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChange)
      CHAIN_MSG_MAP(CCustomTabCtrl<CFolderTabCtrl>)
      CHAIN_MSG_MAP_ALT(CCustomDraw<CFolderTabCtrl>, 1)
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW /*lpNMCustomDraw*/)
   {
      return CDRF_NOTIFYITEMDRAW;   // We need per-item notifications
   }

   DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      CDCHandle dc( lpNMCustomDraw->hdc );
      bool bSelected = lpNMCustomDraw->uItemState & CDIS_SELECTED;

      COLORREF bgColor = ::GetSysColor(bSelected ? COLOR_WINDOW     : COLOR_3DFACE);
      COLORREF fgColor = ::GetSysColor(bSelected ? COLOR_WINDOWTEXT : COLOR_BTNTEXT);

      CBrush brush;
      brush.CreateSolidBrush(bgColor);     // background brush
      dc.SetBkColor(bgColor);              // text background
      dc.SetTextColor(fgColor);            // text color = fg color

      CPen shadowPen;
      shadowPen.CreatePen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));

      // Fill trapezoid
      POINT pts[4];
      RECT rc = lpNMCustomDraw->rc;
      _GetTrapezoid(rc, pts);
      CPenHandle hOldPen = dc.SelectStockPen(BLACK_PEN);   
      CRgn rgn;
      rgn.CreatePolygonRgn(pts, 4, WINDING);
      dc.FillRgn(rgn, brush);

      // Draw edges. This requires two corrections:
      // 1) Trapezoid dimensions don't include the right and bottom edges,
      //    so must use one pixel less on bottom (cybottom)
      // 2) the endpoint of LineTo is not included when drawing the line, so
      //    must add one pixel (cytop)
      pts[1].y--;       // correction #1: true bottom edge y-coord
      pts[2].y--;       // ...ditto
      pts[3].y--;       // correction #2: extend final LineTo
      dc.MoveTo(pts[0]);              // upper left
      dc.LineTo(pts[1]);              // bottom left
      dc.SelectPen(shadowPen);        // bottom line is shadow color
      dc.MoveTo(pts[1]);              // line is inside trapezoid bottom
      dc.LineTo(pts[2]);              // ...
      dc.SelectStockPen(BLACK_PEN);   // upstroke is black
      dc.LineTo(pts[3]);              // y-1 to include endpoint
      if( !bSelected ) {
         // If not highlighted, upstroke has a 3D shadow, one pixel inside
         pts[2].x--;    // offset left one pixel
         pts[3].x--;    // ...ditto
         dc.SelectPen(shadowPen);
         dc.MoveTo(pts[2]);
         dc.LineTo(pts[3]);
      }
      dc.SelectPen(hOldPen);

      TCITEM item = { 0 };
      TCHAR szText[128];
      item.mask = TCIF_TEXT;
      item.pszText = szText;
      item.cchTextMax = sizeof(szText)/sizeof(TCHAR);
      GetItem(lpNMCustomDraw->dwItemSpec, &item);

      // Draw text
      ::InflateRect(&rc, -(CXOFFSET + CXMARGIN), -CYMARGIN);
      dc.DrawText(item.pszText, ::lstrlen(item.pszText), &rc, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

      return CDRF_SKIPDEFAULT;
   }

   void _GetTrapezoid(const RECT& rc, POINT* pts) const
   {
      pts[0].x = rc.left;
      pts[0].y = rc.top;
      pts[1].x = rc.left + CXOFFSET;
      pts[1].y = rc.bottom;
      pts[2].x = rc.right - CXOFFSET - 1;
      pts[2].y = rc.bottom;
      pts[3].x = rc.right - 1, rc.top;
      pts[3].y = rc.top;
   }

   LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // Initialize font
      if( !m_font.IsNull() ) m_font.DeleteObject();
      LOGFONT lf = { 0 };      
      lf.lfHeight = ::GetSystemMetrics(SM_CYHSCROLL) - CYMARGIN;
      lf.lfWeight = FW_NORMAL;
      lf.lfCharSet = DEFAULT_CHARSET;
      ::lstrcpy(lf.lfFaceName, _T("Arial"));
      m_font.CreateFontIndirect(&lf);
 /*     
      NONCLIENTMETRICS ncm = { 0 };
      ncm.cbSize = sizeof(ncm);
      ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
      ncm.lfSmCaptionFont.lfWeight = FW_NORMAL;
      m_font.CreateFontIndirect(&ncm.lfSmCaptionFont);
 */
      SetFont(m_font);

      TC_SIZES sizes;
      GetSizeSettings(&sizes);
      sizes.iPadding = CXOFFSET + 3;
      sizes.iMargin = -CXOFFSET;
      SetSizeSettings(&sizes);

      Invalidate();
      return 0;
   }
};

class CSimpleDotNetTabCtrl : 
   public CCustomTabCtrl<CSimpleDotNetTabCtrl>,
   public CCustomDraw<CSimpleDotNetTabCtrl>
{
public:
   DECLARE_WND_SUPERCLASS(_T("WTL_SimpleDotNetTabCtrl"), GetWndClassName())  

   CFont m_font;
   CFont m_fontBold;
   CBrush m_hbrBack;

   BEGIN_MSG_MAP(CSimpleDotNetTabCtrl)
      CHAIN_MSG_MAP(CCustomTabCtrl<CSimpleDotNetTabCtrl>)
      CHAIN_MSG_MAP_ALT(CCustomDraw<CSimpleDotNetTabCtrl>, 1)
      REFLECTED_NOTIFY_CODE_HANDLER(TCN_INITIALIZE, OnInitialize)
      DEFAULT_REFLECTION_HANDLER()
   END_MSG_MAP()

   LRESULT OnInitialize(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)
   {
      TC_SIZES sizes;
      GetSizeSettings(&sizes);
      sizes.iIndent = 6;
      sizes.iPadding = 0;
      sizes.iMargin = 2;
      sizes.iSelMargin = 4;
      SetSizeSettings(&sizes);
      return 0;
   }

   DWORD OnPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      CDCHandle dc( lpNMCustomDraw->hdc );

      RECT rc;
      GetClientRect(&rc);
      HBRUSH hOldBrush = dc.SelectBrush(m_hbrBack);
      dc.PatBlt(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, PATCOPY);
      dc.SelectBrush(hOldBrush);

      dc.SetTextColor(::GetSysColor(COLOR_BTNTEXT));
      ::SetBkMode(lpNMCustomDraw->hdc, TRANSPARENT);

      return CDRF_NOTIFYITEMDRAW;   // We need per-item notifications
   }

   DWORD OnItemPrePaint(int /*idCtrl*/, LPNMCUSTOMDRAW lpNMCustomDraw)
   {
      CDCHandle dc( lpNMCustomDraw->hdc );
      bool bSelected = lpNMCustomDraw->uItemState & CDIS_SELECTED;
      RECT &rc = lpNMCustomDraw->rc;

      dc.FillRect(&rc, m_hbrBack);
      if( bSelected ) {
         // Tab is selected, so paint tab folder
         if( GetStyle() & TCS_BOTTOM ) 
         {
            RECT rcTab = rc;
            rcTab.bottom -= 3;
            rcTab.right--;
            dc.FillRect(&rcTab, ::GetSysColorBrush(COLOR_BTNFACE));
            dc.SelectStockPen(WHITE_PEN);
            dc.MoveTo(rcTab.left, rcTab.top);
            dc.LineTo(rcTab.left, rcTab.bottom);
            dc.SelectStockPen(BLACK_PEN);
            dc.LineTo(rcTab.right, rcTab.bottom);
            dc.LineTo(rcTab.right, rcTab.top);
         }
         else
         {
            RECT rcTab = rc;
            rcTab.top += 5;
            rcTab.right--;
            dc.FillRect(&rcTab, ::GetSysColorBrush(COLOR_BTNFACE));
            dc.SelectStockPen(WHITE_PEN);
            dc.MoveTo(rcTab.left, rcTab.bottom);
            dc.LineTo(rcTab.left, rcTab.top);
            dc.LineTo(rcTab.right, rcTab.top);
            dc.SelectStockPen(BLACK_PEN);
            dc.LineTo(rcTab.right, rcTab.bottom);
         }
      }

      TCITEM item = { 0 };
      TCHAR szText[128] = { 0 };
      item.mask = TCIF_TEXT;
      item.pszText = szText;
      item.cchTextMax = (sizeof(szText)/sizeof(TCHAR)) - 1;
      GetItem(lpNMCustomDraw->dwItemSpec, &item);

      TC_SIZES sizes;
      GetSizeSettings(&sizes);

      HFONT hOldFont = dc.SelectFont(bSelected ? m_fontBold : m_font);
      RECT rcText = rc;
      ::InflateRect(&rcText, -sizes.iPadding, 0);
      if( GetStyle() & TCS_BOTTOM ) {
         rcText.top += 2; 
         dc.DrawText(item.pszText, -1, &rcText, DT_CENTER | DT_TOP | DT_SINGLELINE);
      }
      else {
         rcText.bottom -= 3;
         dc.DrawText(item.pszText, -1, &rcText, DT_CENTER | DT_BOTTOM | DT_SINGLELINE);
      }
      dc.SelectFont(hOldFont);

      return CDRF_SKIPDEFAULT;
   }

   LRESULT OnSettingChange(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
   {
      // Initialize font
      if( !m_font.IsNull() ) m_font.DeleteObject();
      if( !m_fontBold.IsNull() ) m_fontBold.DeleteObject();
      NONCLIENTMETRICS ncm = { 0 };
      ncm.cbSize = sizeof(ncm);
      ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
      ncm.lfSmCaptionFont.lfWeight = FW_NORMAL;
      m_font.CreateFontIndirect(&ncm.lfSmCaptionFont);
      ncm.lfSmCaptionFont.lfWeight = FW_BOLD;
      m_fontBold.CreateFontIndirect(&ncm.lfSmCaptionFont);
      SetFont(m_fontBold); // Bold font scales tabs correctly

      // Background brush
      if( !m_hbrBack.IsNull() ) m_hbrBack.DeleteObject();
      CWindowDC dc(NULL);
      int nBitsPerPixel = dc.GetDeviceCaps(BITSPIXEL);
      if( nBitsPerPixel > 8 ) {
         COLORREF clrBtnHilite = ::GetSysColor(COLOR_BTNHILIGHT);
         COLORREF clrBtnFace = ::GetSysColor(COLOR_BTNFACE);
         COLORREF clrLight = 
            RGB( GetRValue(clrBtnFace) + ((GetRValue(clrBtnHilite) - GetRValue(clrBtnFace)) / 2),
                 GetGValue(clrBtnFace) + ((GetGValue(clrBtnHilite) - GetGValue(clrBtnFace)) / 2),
                 GetBValue(clrBtnFace) + ((GetBValue(clrBtnHilite) - GetBValue(clrBtnFace)) / 2)
         );
         m_hbrBack.CreateSolidBrush(clrLight);
      }
      else {
         m_hbrBack =  CDCHandle::GetHalftoneBrush();
      }
      Invalidate();
      return 0;
   }
};


#endif // __COOLTABCTRLS_H__

